﻿using LightCAD.Runtime;
using System;
using System.Collections;
using System.Collections.Generic;
using EventArgs = LightCAD.Three.EventArgs;

namespace LightCAD.Model
{
    public class SuControls : EventDispatcher, ILightCADControls
    {
        public static class STATE
        {
            public const int NONE = -1;
            public const int ROTATE = 0;
            public const int DOLLY = 1;
            public const int PAN = 2;
            public const int TOUCH_ROTATE = 3;
            public const int TOUCH_PAN = 4;
            public const int TOUCH_DOLLY_PAN = 5;
            public const int TOUCH_DOLLY_ROTATE = 6;
        }


        public Camera _object;
        public IDocument3dEditorControl glControl;
        public bool enabled;
        public Vector3 target;
        public double minDistance;
        public double maxDistance;
        public double minZoom;
        public double maxZoom;
        public double minPolarAngle;
        public double maxPolarAngle;
        public double minAzimuthAngle;
        public double maxAzimuthAngle;
        public bool enableDamping;
        public double dampingFactor;
        public bool enableZoom;
        public double zoomSpeed;
        public bool enableRotate;
        public double rotateSpeed;
        public bool enablePan;
        public double panSpeed;
        public bool screenSpacePanning;
        public double keyPanSpeed;
        public bool autoRotate;
        public double autoRotateSpeed;
        //public object keys;
        //public object mouseButtons;
        //public object touches;
        public Vector3 target0;
        public Vector3 position0;
        public double zoom0;
        public object _domElementKeyEvents;

        //
        // internals
        //

        public int state = STATE.NONE;
        public double EPS = 0.000001;

        // current position in spherical coordinates
        public Spherical spherical = new Spherical();
        public Spherical sphericalDelta = new Spherical();

        public double scale = 1;
        public Vector3 panOffset = new Vector3();
        public bool zoomChanged = false;

        public Vector2 rotateStart = new Vector2();
        public Vector2 rotateEnd = new Vector2();
        public Vector2 rotateDelta = new Vector2();

        public Vector2 panStart = new Vector2();
        public Vector2 panEnd = new Vector2();
        public Vector2 panDelta = new Vector2();

        public Vector2 dollyStart = new Vector2();
        public Vector2 dollyEnd = new Vector2();
        public Vector2 dollyDelta = new Vector2();

        public ListEx<object> pointers = new ListEx<object>();
        public JsObj<object> pointerPositions = new JsObj<object>();

        public EventArgs _changeEvent = new EventArgs { type = "change" };
        public EventArgs _startEvent = new EventArgs { type = "start" };
        public EventArgs _endEvent = new EventArgs { type = "end" };

        public SuControls(Camera _object, IDocument3dEditorControl glControl, Vector3 initTarget = null)
        {
            this._object = _object;
            this.glControl = glControl;

            // Set to false to disable this control
            this.enabled = true;

            // "target" sets the location of focus, where the object orbits around
            this.target = new Vector3();
            if (initTarget != null)
                target.Copy(initTarget);
            // How far you can dolly in and out ( PerspectiveCamera only )
            this.minDistance = 0;
            this.maxDistance = double.MaxValue;

            // How far you can zoom in and out ( OrthographicCamera only )
            this.minZoom = 0;
            this.maxZoom = double.MaxValue;

            // How far you can orbit vertically, upper and lower limits.
            // Range is 0 to Math.PI radians.
            this.minPolarAngle = 0; // radians
            this.maxPolarAngle = Math.PI; // radians

            // How far you can orbit horizontally, upper and lower limits.
            // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
            this.minAzimuthAngle = double.MinValue; // radians
            this.maxAzimuthAngle = double.MaxValue; // radians

            // Set to true to enable damping (inertia)
            // If damping is enabled, you must call controls.update() in your animation loop
            this.enableDamping = false;
            this.dampingFactor = 0.05;

            // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
            // Set to false to disable zooming
            this.enableZoom = true;
            this.zoomSpeed = 1.0;

            // Set to false to disable rotating
            this.enableRotate = true;
            this.rotateSpeed = 1.0;

            // Set to false to disable panning
            this.enablePan = true;
            this.panSpeed = 1.0;
            this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
            this.keyPanSpeed = 7.0; // pixels moved per arrow key push

            // Set to true to automatically rotate around the target
            // If auto-rotate is enabled, you must call controls.update() in your animation loop
            this.autoRotate = false;
            this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60

            // The four arrow keys
            //this.keys = new { LEFT = "ArrowLeft", UP = "ArrowUp", RIGHT = "ArrowRight", BOTTOM = "ArrowDown" };

            // Mouse buttons
            //this.mouseButtons = null;// { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };

            // Touch fingers
            //this.touches = null;// { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };

            // for reset
            this.target0 = this.target.Clone();
            this.position0 = this._object.position.Clone();
            this.zoom0 = this._object.zoom;

            // the target DOM element for key events
            this._domElementKeyEvents = null;

            //  this.glControl.ContextMenuStripChanged += onContextMenu;
            this.glControl.AttachEvents(null, null, onMouseEvent, onKeyDown, null, null, null);
            this.update();//初始化相机LookAt,避免相机跳跃
        }

        public double getPolarAngle()
        {
            return spherical.Phi;
        }

        public double getAzimuthalAngle()
        {
            return spherical.Theta;
        }

        public double getDistance()
        {

            return this._object.position.DistanceTo(this.target);

        }

        public void clearState() 
        {
            this.state = STATE.NONE;
        }
        public void saveState()
        {

            this.target0.Copy(this.target);
            this.position0.Copy(this._object.position);
            this.zoom0 = this._object.zoom;

        }

        public void reset()
        {

            this.target.Copy(this.target0);
            this._object.position.Copy(this.position0);
            this._object.zoom = this.zoom0;

            this._object.updateProjectionMatrix();
            this.dispatchEvent(_changeEvent);

            this.update();

            state = STATE.NONE;

        }

        // this method is exposed, but perhaps it would be better if we can make it private...
        public bool update()
        {
            var offset = new Vector3();

            // so camera.up is the orbit axis
            var quat = new Quaternion().SetFromUnitVectors(this._object.up, new Vector3(0, 1, 0));
            var quatInverse = quat.Clone().Invert();

            var lastPosition = new Vector3();
            var lastQuaternion = new Quaternion();


            var twoPI = 2 * Math.PI;

            var position = this._object.position;

            offset.Copy(position).Sub(this.target);

            // rotate offset to "y-axis-is-up" space
            offset.ApplyQuaternion(quat);

            // angle from z-axis around y-axis
            spherical.SetFromVector3(offset);

            if (this.autoRotate && state == STATE.NONE)
            {

                rotateLeft(getAutoRotationAngle());

            }

            if (this.enableDamping)
            {

                spherical.Theta += sphericalDelta.Theta * this.dampingFactor;
                spherical.Phi += sphericalDelta.Phi * this.dampingFactor;

            }
            else
            {

                spherical.Theta += sphericalDelta.Theta;
                spherical.Phi += sphericalDelta.Phi;

            }

            // restrict theta to be between desired limits

            var min = this.minAzimuthAngle;
            var max = this.maxAzimuthAngle;

            if (MathEx.IsFinite(min) && MathEx.IsFinite(max))
            {

                if (min < -Math.PI) min += twoPI; else if (min > Math.PI) min -= twoPI;

                if (max < -Math.PI) max += twoPI; else if (max > Math.PI) max -= twoPI;

                if (min <= max)
                {

                    spherical.Theta = Math.Max(min, Math.Min(max, spherical.Theta));

                }
                else
                {

                    spherical.Theta = (spherical.Theta > (min + max) / 2) ?
                        Math.Max(min, spherical.Theta) :
                        Math.Min(max, spherical.Theta);

                }

            }

            // restrict phi to be between desired limits
            spherical.Phi = Math.Max(this.minPolarAngle, Math.Min(this.maxPolarAngle, spherical.Phi));

            spherical.MakeSafe();


            spherical.Radius *= scale;

            // restrict radius to be between desired limits
            spherical.Radius = Math.Max(this.minDistance, Math.Min(this.maxDistance, spherical.Radius));

            // move target to panned location

            if (this.enableDamping)
            {
                this.target.AddScaledVector(panOffset, this.dampingFactor);
            }
            else
            {
                this.target.Add(panOffset);
            }

            offset.SetFromSpherical(spherical);

            // rotate offset back to "camera-up-vector-is-up" space
            offset.ApplyQuaternion(quatInverse);

            position.Copy(this.target).Add(offset);

            this._object.lookAt(this.target);

            if (this.enableDamping)
            {
                sphericalDelta.Theta *= (1 - this.dampingFactor);
                sphericalDelta.Phi *= (1 - this.dampingFactor);
                panOffset.MulScalar(1 - this.dampingFactor);
            }
            else
            {
                sphericalDelta.Set(0, 0, 0);
                panOffset.Set(0, 0, 0);
            }

            scale = 1;

            // update condition is:
            // min(camera displacement, camera rotation in radians)^2 > EPS
            // using small-angle approximation cos(x/2) = 1 - x^2 / 8

            if (zoomChanged ||
                lastPosition.DistanceToSquared(this._object.position) > EPS ||
                8 * (1 - lastQuaternion.Dot(this._object.quaternion)) > EPS)
            {

                this.dispatchEvent(_changeEvent);

                lastPosition.Copy(this._object.position);
                lastQuaternion.Copy(this._object.quaternion);
                zoomChanged = false;

                return true;

            }

            return false;
        }

        public void dispose()
        {
            this.glControl.DetachEvents(null, null, onMouseEvent, onKeyDown, null, null, null);

        }

        private double getAutoRotationAngle()
        {

            return 2 * Math.PI / 60 / 60 * this.autoRotateSpeed;

        }

        private double getZoomScale()
        {

            return Math.Pow(0.95, this.zoomSpeed);

        }

        private void rotateLeft(double angle)
        {

            sphericalDelta.Theta -= angle;

        }

        private void rotateUp(double angle)
        {

            sphericalDelta.Phi -= angle;

        }

        private void panLeft(double distance, Matrix4 objectMatrix)
        {
            var v = new Vector3();
            v.SetFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
            v.MulScalar(-distance);

            panOffset.Add(v);
        }

        private void panUp(double distance, Matrix4 objectMatrix)
        {
            var v = new Vector3();
            if (this.screenSpacePanning)
            {

                v.SetFromMatrixColumn(objectMatrix, 1);

            }
            else
            {

                v.SetFromMatrixColumn(objectMatrix, 0);
                v.CrossVectors(this._object.up, v);

            }

            v.MulScalar(distance);

            panOffset.Add(v);

        }


        // deltaX and deltaY are in pixels; right and down are positive
        private void pan(double deltaX, double deltaY)
        {
            var offset = new Vector3();
            var element = this.glControl.EditorBounds;

            if (this._object is PerspectiveCamera)
            {

                var perCamera = this._object as PerspectiveCamera;
                // perspective
                var position = perCamera.position;
                offset.Copy(position).Sub(this.target);
                var targetDistance = offset.Length();

                // half of the fov is center to top of screen
                targetDistance *= Math.Tan((perCamera.fov / 2) * Math.PI / 180.0);

                // we use only clientHeight here so aspect ratio does not distort speed
                panLeft(2 * deltaX * targetDistance / element.Height, perCamera.matrix);
                panUp(2 * deltaY * targetDistance / element.Height, perCamera.matrix);

            }
            else if (this._object is OrthographicCamera)
            {
                var ortCamera = this._object as OrthographicCamera;
                // orthographic
                panLeft(deltaX * (ortCamera.right - ortCamera.left) / ortCamera.zoom / element.Width, ortCamera.matrix);
                panUp(deltaY * (ortCamera.top - ortCamera.bottom) / ortCamera.zoom / element.Height, ortCamera.matrix);

            }
            else
            {

                // camera neither orthographic nor perspective
                console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.");
                this.enablePan = false;

            }

        }


        private void dollyOut(double dollyScale)
        {

            if (this._object is PerspectiveCamera)
            {

                scale /= dollyScale;

            }
            else if (this._object is OrthographicCamera)
            {

                this._object.zoom = Math.Max(this.minZoom, Math.Min(this.maxZoom, this._object.zoom * dollyScale));
                this._object.updateProjectionMatrix();
                zoomChanged = true;

            }
            else
            {

                console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.");
                this.enableZoom = false;

            }

        }

        private void dollyIn(double dollyScale)
        {

            if (this._object is PerspectiveCamera)
            {

                scale *= dollyScale;

            }
            else if (this._object is OrthographicCamera)
            {

                this._object.zoom = Math.Max(this.minZoom, Math.Min(this.maxZoom, this._object.zoom / dollyScale));
                this._object.updateProjectionMatrix();
                zoomChanged = true;

            }
            else
            {

                console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.");
                this.enableZoom = false;

            }

        }

        #region event callbacks - update the object state

        private void handleMouseDownRotate(MouseEventRuntime e)
        {

            rotateStart.Set(e.X, e.Y);

        }
        private void handleMouseDownDolly(MouseEventRuntime e)
        {

            dollyStart.Set(e.X, e.Y);

        }

        private void handleMouseDownPan(MouseEventRuntime e)
        {

            panStart.Set(e.X, e.Y);

        }

        private void handleMouseMoveRotate(MouseEventRuntime e)
        {

            rotateEnd.Set(e.X, e.Y);

            rotateDelta.SubVectors(rotateEnd, rotateStart).MultiplyScalar(this.rotateSpeed);

            var element = this.glControl.EditorBounds;

            rotateLeft(2 * Math.PI * rotateDelta.X / element.Height); // yes, height

            rotateUp(2 * Math.PI * rotateDelta.Y / element.Height);

            rotateStart.Copy(rotateEnd);

            this.update();

        }

        private void handleMouseMoveDolly(MouseEventRuntime e)
        {

            dollyEnd.Set(e.X, e.Y);

            dollyDelta.SubVectors(dollyEnd, dollyStart);

            if (dollyDelta.Y > 0)
            {

                dollyOut(getZoomScale());

            }
            else if (dollyDelta.Y < 0)
            {

                dollyIn(getZoomScale());

            }

            dollyStart.Copy(dollyEnd);

            this.update();

        }

        private void handleMouseMovePan(MouseEventRuntime e)
        {

            panEnd.Set(e.X, e.Y);

            panDelta.SubVectors(panEnd, panStart).MultiplyScalar(this.panSpeed);

            pan(panDelta.X, panDelta.Y);

            panStart.Copy(panEnd);

            this.update();

        }

        private void dynamicHandleMouseWheel(MouseEventRuntime e)
        {
            var location = new Vector2();
            location.X = ((double)e.X / this.glControl.EditorBounds.Width) * 2 - 1;
            location.Y = -((double)e.Y / this.glControl.EditorBounds.Height) * 2 + 1;
            double wheelDetla = (e.DeltaY / 1200);
            var zoomScale = 1 + wheelDetla;
            var newZoom = _object.zoom * (zoomScale);
            if (newZoom < 0.001)
                newZoom = 0.001;
            if (newZoom > 100)
                newZoom = 100;
            zoomScale = newZoom / _object.zoom;
            wheelDetla = zoomScale - 1;
            var sign = Math.Sign(wheelDetla);
            var moveDir = new Vector3(location.X, location.Y, 0);
            var unProjectionMat = _object.projectionMatrixInverse;
            moveDir.ApplyMatrix3(new Matrix3().SetFromMatrix4(unProjectionMat));
            moveDir.ApplyMatrix3(new Matrix3().SetFromMatrix4(_object.matrixWorld));
            _object.position.AddScaledVector(moveDir, wheelDetla);
            target.AddScaledVector(moveDir, wheelDetla);
            _object.zoom *= zoomScale;
            _object.updateMatrix();
            _object.updateMatrixWorld();
            _object.updateProjectionMatrix();

            this.glControl.RefreshControl();
            //this.update();
        }

        private void handleMouseWheel(MouseEventRuntime e)
        {

            if (e.DeltaY > 0)
            {

                dollyIn(getZoomScale());

            }
            else if (e.DeltaY < 0)
            {

                dollyOut(getZoomScale());

            }

            this.update();

        }

        private void handleKeyDown(KeyEventRuntime e)
        {
            var element = this.glControl.EditorBounds;
            var needsUpdate = false;

            switch (e.KeyCode)
            {
                case (int)LcKey.Up:
                    if (e.KeyModifiers == LcKeyModifiers.Control)
                        rotateUp(2 * Math.PI * this.rotateSpeed / element.Height);
                    else
                        pan(0, this.keyPanSpeed);
                    needsUpdate = true;
                    break;

                case (int)LcKey.Down:
                    if (e.KeyModifiers == LcKeyModifiers.Control)
                        rotateUp(-2 * Math.PI * this.rotateSpeed / element.Height);
                    else
                        pan(0, -this.keyPanSpeed);
                    needsUpdate = true;
                    break;
                case (int)LcKey.Left:
                    if (e.KeyModifiers == LcKeyModifiers.Control)
                        rotateLeft(2 * Math.PI * this.rotateSpeed / element.Height);
                    else
                        pan(this.keyPanSpeed, 0);
                    needsUpdate = true;
                    break;

                case (int)LcKey.Right:
                    if (e.KeyModifiers == LcKeyModifiers.Control)
                        rotateLeft(-2 * Math.PI * this.rotateSpeed / element.Height);
                    else
                        pan(-this.keyPanSpeed, 0);
                    needsUpdate = true;
                    break;

            }

            if (needsUpdate)
            {
                // prevent the browser from scrolling on cursor keys
                //e.preventDefault();
                this.update();
            }
        }

        private void handleTouchStartRotate()
        {

            //if (pointers.length == 1)
            //{

            //    rotateStart.set(pointers[0].pageX, pointers[0].pageY);

            //}
            //else
            //{

            //    var x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
            //    var y = 0.5 * (pointers[0].pageY + pointers[1].pageY);

            //    rotateStart.set(x, y);

            //}

        }

        private void handleTouchStartPan()
        {

            //if (pointers.length == 1)
            //{

            //    panStart.set(pointers[0].pageX, pointers[0].pageY);

            //}
            //else
            //{

            //    var x = 0.5 * (pointers[0].pageX + pointers[1].pageX);
            //    var y = 0.5 * (pointers[0].pageY + pointers[1].pageY);

            //    panStart.set(x, y);

            //}

        }

        private void handleTouchStartDolly()
        {

            //var dx = pointers[0].pageX - pointers[1].pageX;
            //var dy = pointers[0].pageY - pointers[1].pageY;

            //var distance = JMath.sqrt(dx * dx + dy * dy);

            //dollyStart.set(0, distance);

        }

        private void handleTouchStartDollyPan()
        {

            if (this.enableZoom) handleTouchStartDolly();

            if (this.enablePan) handleTouchStartPan();

        }

        private void handleTouchStartDollyRotate()
        {

            if (this.enableZoom) handleTouchStartDolly();

            if (this.enableRotate) handleTouchStartRotate();

        }

        private void handleTouchMoveRotate(object e)
        {

            //if (pointers.length == 1)
            //{

            //    rotateEnd.set(e.pageX, e.pageY);

            //}
            //else
            //{

            //    var position = getSecondPointerPosition(e);

            //    var x = 0.5 * (e.pageX + position.x);
            //    var y = 0.5 * (e.pageY + position.y);

            //    rotateEnd.set(x, y);

            //}
            //rotateDelta.subVectors(rotateEnd, rotateStart).multiplyScalar(this.rotateSpeed);

            //var element = this.glControl;

            //rotateLeft(2 * Math.PI * rotateDelta.x / element.Height); // yes, height

            //rotateUp(2 * Math.PI * rotateDelta.y / element.Height);

            //rotateStart.copy(rotateEnd);
        }

        private void handleTouchMovePan(object e)
        {

            //if (pointers.length == 1)
            //{

            //    panEnd.set(e.pageX, e.pageY);
            //}
            //else
            //{

            //    var position = getSecondPointerPosition(e);

            //    var x = 0.5 * (e.pageX + position.x);
            //    var y = 0.5 * (e.pageY + position.y);

            //    panEnd.set(x, y);

            //}

            //panDelta.subVectors(panEnd, panStart).multiplyScalar(this.panSpeed);

            //pan(panDelta.x, panDelta.y);

            //panStart.copy(panEnd);

        }

        private void handleTouchMoveDolly(object e)
        {

            //var position = getSecondPointerPosition(e);

            //var dx = e.pageX - position.x;
            //var dy = e.pageY - position.y;

            //var distance = JMath.sqrt(dx * dx + dy * dy);

            //dollyEnd.set(0, distance);

            //dollyDelta.set(0, JMath.pow(dollyEnd.y / dollyStart.y, this.zoomSpeed));

            //dollyOut(dollyDelta.y);

            //dollyStart.copy(dollyEnd);

        }

        private void handleTouchMoveDollyPan(object e)
        {

            if (this.enableZoom) handleTouchMoveDolly(e);

            if (this.enablePan) handleTouchMovePan(e);

        }

        private void handleTouchMoveDollyRotate(object e)
        {

            if (this.enableZoom) handleTouchMoveDolly(e);

            if (this.enableRotate) handleTouchMoveRotate(e);

        }
        #endregion

        #region event handlers - FSM: listen for events and reset state
        private void onPointerDown(object e)
        {

            //if (this.enabled == false) return;

            //if (pointers.length == 0)
            //{

            //    this.glControl.setPointerCapture(e.pointerId);

            //    this.glControl.addEventListener("pointermove", onPointerMove);
            //    this.glControl.addEventListener("pointerup", onPointerUp);

            //}

            ////

            //addPointer(e);

            //if (e.pointerType == "touch")
            //{

            //    onTouchStart(e);

            //}
            //else
            //{

            //    onMouseDown(e);

            //}

        }

        private void onPointerMove(object e)
        {

            //if (this.enabled == false) return;

            //if (e.pointerType == "touch")
            //{

            //    onTouchMove(e);

            //}
            //else
            //{

            //    onMouseMove(e);

            //}

        }
 
        public void onMouseEvent(string type,MouseEventRuntime e)
        {
            if (type == "Down") onMouseDown(e);
            else if (type == "Move") onMouseMove(e);
            else if (type == "Up") onMouseUp(e);
            else if (type == "Wheel") onMouseWheel(e);
        }
        public void onMouseDown( MouseEventRuntime e)
        {
            
            if (this.enabled == false) return;
            switch (e.Button)
            {
                case LcMouseButtons.Middle:
                    if (e.KeyModifiers == LcKeyModifiers.Shift)
                    {
                        if (this.enablePan == false) return;
                        handleMouseDownPan(e);
                        state = STATE.PAN;
                    }
                    else
                    {
                        if (this.enableRotate == false) return;
                        handleMouseDownRotate(e);
                        state = STATE.ROTATE;
                    }

                    break;

                case LcMouseButtons.Right:

                    //if (e.KeyModifiers == LcKeyModifiers.Shift)
                    //{
                    //        if (this.enableRotate == false) return;

                    //        handleMouseDownRotate(e);

                    //        state = STATE.ROTATE;
                    //}
                    break;
                default:
                    state = STATE.NONE;
                    break;

            }

            if (state != STATE.NONE)
            {

                this.dispatchEvent(_startEvent);

            }

        }
        public void onMouseUp( MouseEventRuntime e)
        {
            if (this.enabled == false) return;
            state = STATE.NONE;

            this.dispatchEvent(_endEvent);
        }

        public void onMouseMove(MouseEventRuntime e)
        {
            if (this.enabled == false) return;
            switch (state)
            {

                case STATE.ROTATE:

                    if (this.enableRotate == false) return;

                    handleMouseMoveRotate(e);

                    break;

                case STATE.DOLLY:

                    if (this.enableZoom == false) return;

                    handleMouseMoveDolly(e);

                    break;

                case STATE.PAN:

                    if (this.enablePan == false) return;

                    handleMouseMovePan(e);

                    break;

            }

        }

        public void onMouseWheel(MouseEventRuntime e)
        {

            if (this.enabled == false || this.enableZoom == false || state != STATE.NONE) return;

            //e.preventDefault();

            this.dispatchEvent(_startEvent);

            dynamicHandleMouseWheel(e);
            //handleMouseWheel(e);

            this.dispatchEvent(_endEvent);

        }

        public void onKeyDown(KeyEventRuntime e)
        {

            if (this.enabled == false || this.enablePan == false) return;

            handleKeyDown(e);

        }
        #endregion

        public void Enable(bool enabled)
        {
            this.enabled = enabled;
        }

        public void EnableTranslate(bool enabled)
        {
            this.enablePan = enabled;
        }

        public void EnableRotate(bool enabled)
        {
            this.enableRotate = enabled;
        }

        public void EnableScale(bool enabled)
        {
            this.enableZoom = enabled;
        }

        public void AddEventListener(string type, Three.EventHandler listener)
        {
            this.addEventListener(type, listener);
        }

        public void Dispose()
        {
            this.dispose();
        }

        public void Update()
        {
            this.update();
        }

        public void ClearState()
        {
            this.clearState();
        }

        public void Focus(Vector3 center, Vector3 normal, Box3 boundingBox)
        {
            var max = boundingBox.Max.DistanceTo(boundingBox.Min);
            var position = center.Clone().AddScaledVector(normal, max);
            _object.position.Copy(position);
            //this.target.Copy(center);
            _object.lookAt(center.X, center.Y, center.Z);
            //_object.up.Copy(normal);
            //_object.zoom = 0.0175;
            _object.updateMatrix();
            _object.updateMatrixWorld();
            _object.updateProjectionMatrix();

            var dir = _object.getWorldDirection();

            this.glControl.RefreshControl();
        }


        public void Clone(ILightCADControls source)
        {
            if (source is OrbitControls)
            {
                var orbCon = (OrbitControls)source;
                this.enabled = orbCon.enabled;
                this.target = orbCon.target.Clone();
                this.minDistance = orbCon.minDistance;
                this.maxDistance = orbCon.maxDistance;
                this.minZoom = orbCon.minZoom;
                this.maxZoom = orbCon.maxZoom;
                this.minPolarAngle = orbCon.minPolarAngle;
                this.maxPolarAngle = orbCon.maxPolarAngle;
                this.minAzimuthAngle = orbCon.minAzimuthAngle;
                this.maxAzimuthAngle = orbCon.maxAzimuthAngle;
                this.enableDamping = orbCon.enableDamping;
                this.dampingFactor = orbCon.dampingFactor;
                this.enableZoom = orbCon.enableZoom;
                this.zoomSpeed = orbCon.zoomSpeed;
                this.enableRotate = orbCon.enableRotate;
                this.rotateSpeed = orbCon.rotateSpeed;
                this.enablePan = orbCon.enablePan;
                this.panSpeed = orbCon.panSpeed;
                this.screenSpacePanning = orbCon.screenSpacePanning;
                this.keyPanSpeed = orbCon.keyPanSpeed;
                this.autoRotate = orbCon.autoRotate;
                this.autoRotateSpeed = orbCon.autoRotateSpeed;
                this.target0 = orbCon.target0.Clone();
                this.position0 = orbCon.position0.Clone();
                this.zoom0 = orbCon.zoom0;
                this._domElementKeyEvents = orbCon._domElementKeyEvents;

                this.state = orbCon.state;
                this.EPS = orbCon.EPS;
                this.spherical = orbCon.spherical.Clone();
                this.sphericalDelta = orbCon.sphericalDelta.Clone();
                this.scale = orbCon.scale;
                this.panOffset = orbCon.panOffset.Clone();
                this.zoomChanged = orbCon.zoomChanged;
                this.rotateStart = orbCon.rotateStart.Clone();
                this.rotateEnd = orbCon.rotateEnd.Clone();
                this.rotateDelta = orbCon.rotateDelta.Clone();
                this.panStart = orbCon.panStart.Clone();
                this.panEnd = orbCon.panEnd.Clone();
                this.panDelta = orbCon.panDelta.Clone();
                this.dollyStart = orbCon.dollyStart.Clone();
                this.dollyEnd = orbCon.dollyEnd.Clone();
                this.dollyDelta = orbCon.dollyDelta.Clone();
                this.pointers = orbCon.pointers.Clone();
                this.pointerPositions = orbCon.pointerPositions.clone();
                this._changeEvent = orbCon._changeEvent;
                this._startEvent = orbCon._startEvent;
                this._endEvent = orbCon._endEvent;
            }
            else if (source is SuControls)
            {
                var suCon = (SuControls)source;
                this.enabled = suCon.enabled;
                this.target = suCon.target.Clone();
                this.minDistance = suCon.minDistance;
                this.maxDistance = suCon.maxDistance;
                this.minZoom = suCon.minZoom;
                this.maxZoom = suCon.maxZoom;
                this.minPolarAngle = suCon.minPolarAngle;
                this.maxPolarAngle = suCon.maxPolarAngle;
                this.minAzimuthAngle = suCon.minAzimuthAngle;
                this.maxAzimuthAngle = suCon.maxAzimuthAngle;
                this.enableDamping = suCon.enableDamping;
                this.dampingFactor = suCon.dampingFactor;
                this.enableZoom = suCon.enableZoom;
                this.zoomSpeed = suCon.zoomSpeed;
                this.enableRotate = suCon.enableRotate;
                this.rotateSpeed = suCon.rotateSpeed;
                this.enablePan = suCon.enablePan;
                this.panSpeed = suCon.panSpeed;
                this.screenSpacePanning = suCon.screenSpacePanning;
                this.keyPanSpeed = suCon.keyPanSpeed;
                this.autoRotate = suCon.autoRotate;
                this.autoRotateSpeed = suCon.autoRotateSpeed;
                this.target0 = suCon.target0.Clone();
                this.position0 = suCon.position0.Clone();
                this.zoom0 = suCon.zoom0;
                this._domElementKeyEvents = suCon._domElementKeyEvents;

                this.state = suCon.state;
                this.EPS = suCon.EPS;
                this.spherical = suCon.spherical.Clone();
                this.sphericalDelta = suCon.sphericalDelta.Clone();
                this.scale = suCon.scale;
                this.panOffset = suCon.panOffset.Clone();
                this.zoomChanged = suCon.zoomChanged;
                this.rotateStart = suCon.rotateStart.Clone();
                this.rotateEnd = suCon.rotateEnd.Clone();
                this.rotateDelta = suCon.rotateDelta.Clone();
                this.panStart = suCon.panStart.Clone();
                this.panEnd = suCon.panEnd.Clone();
                this.panDelta = suCon.panDelta.Clone();
                this.dollyStart = suCon.dollyStart.Clone();
                this.dollyEnd = suCon.dollyEnd.Clone();
                this.dollyDelta = suCon.dollyDelta.Clone();
                this.pointers = suCon.pointers.Clone();
                this.pointerPositions = suCon.pointerPositions.clone();
                this._changeEvent = suCon._changeEvent;
                this._startEvent = suCon._startEvent;
                this._endEvent = suCon._endEvent;
            }
            else if (source is RvtControls)
            {
                var rvtCon = (RvtControls)source;
                this.enabled = rvtCon.enabled;
                this.target = rvtCon.target.Clone();
                this.minDistance = rvtCon.minDistance;
                this.maxDistance = rvtCon.maxDistance;
                this.minZoom = rvtCon.minZoom;
                this.maxZoom = rvtCon.maxZoom;
                this.minPolarAngle = rvtCon.minPolarAngle;
                this.maxPolarAngle = rvtCon.maxPolarAngle;
                this.minAzimuthAngle = rvtCon.minAzimuthAngle;
                this.maxAzimuthAngle = rvtCon.maxAzimuthAngle;
                this.enableDamping = rvtCon.enableDamping;
                this.dampingFactor = rvtCon.dampingFactor;
                this.enableZoom = rvtCon.enableZoom;
                this.zoomSpeed = rvtCon.zoomSpeed;
                this.enableRotate = rvtCon.enableRotate;
                this.rotateSpeed = rvtCon.rotateSpeed;
                this.enablePan = rvtCon.enablePan;
                this.panSpeed = rvtCon.panSpeed;
                this.screenSpacePanning = rvtCon.screenSpacePanning;
                this.keyPanSpeed = rvtCon.keyPanSpeed;
                this.autoRotate = rvtCon.autoRotate;
                this.autoRotateSpeed = rvtCon.autoRotateSpeed;
                this.target0 = rvtCon.target0.Clone();
                this.position0 = rvtCon.position0.Clone();
                this.zoom0 = rvtCon.zoom0;
                this._domElementKeyEvents = rvtCon._domElementKeyEvents;

                this.state = rvtCon.state;
                this.EPS = rvtCon.EPS;
                this.spherical = rvtCon.spherical.Clone();
                this.sphericalDelta = rvtCon.sphericalDelta.Clone();
                this.scale = rvtCon.scale;
                this.panOffset = rvtCon.panOffset.Clone();
                this.zoomChanged = rvtCon.zoomChanged;
                this.rotateStart = rvtCon.rotateStart.Clone();
                this.rotateEnd = rvtCon.rotateEnd.Clone();
                this.rotateDelta = rvtCon.rotateDelta.Clone();
                this.panStart = rvtCon.panStart.Clone();
                this.panEnd = rvtCon.panEnd.Clone();
                this.panDelta = rvtCon.panDelta.Clone();
                this.dollyStart = rvtCon.dollyStart.Clone();
                this.dollyEnd = rvtCon.dollyEnd.Clone();
                this.dollyDelta = rvtCon.dollyDelta.Clone();
                this.pointers = rvtCon.pointers.Clone();
                this.pointerPositions = rvtCon.pointerPositions.clone();
                this._changeEvent = rvtCon._changeEvent;
                this._startEvent = rvtCon._startEvent;
                this._endEvent = rvtCon._endEvent;
            }
        }
    }
}
